﻿unit Main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, IdBaseComponent, IdComponent, IdCustomTCPServer,
  IdCustomHTTPServer, IdHTTPServer, IdContext, JSON, HttpApp, IdURI, IniFiles,
  Printers, mprint, EncdDecd,
  Vcl.Samples.Spin;

type
  TMainForm = class(TForm)
    Label1: TLabel;
    ComboBox_Port: TComboBox;
    GroupBox_IdCard: TGroupBox;
    Image_Photo: TImage;
    Button_Read: TButton;
    Button_Clear: TButton;
    Label2: TLabel;
    ComboBox_Baud: TComboBox;
    IdHTTPServer1: TIdHTTPServer;
    GroupBox2: TGroupBox;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    LinkLabel2: TLinkLabel;
    LinkLabel3: TLinkLabel;
    LinkLabel4: TLinkLabel;
    LinkLabel5: TLinkLabel;
    offXSE: TSpinEdit;
    offYSE: TSpinEdit;
    printerCom: TComboBox;
    memo_msg: TMemo;
    Label6: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure Button_ReadClick(Sender: TObject);
    function read(): TJSONObject;
    procedure Button_ClearClick(Sender: TObject);
    procedure IdHTTPServer1CommandGet(AContext: TIdContext;
      ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);

    procedure loadSetting();
    procedure saveSetting();
    procedure offXSEChange(Sender: TObject);
    procedure offYSEChange(Sender: TObject);
    procedure LinkLabel2Click(Sender: TObject);
    procedure LinkLabel3Click(Sender: TObject);
    procedure LinkLabel4Click(Sender: TObject);
    procedure LinkLabel5Click(Sender: TObject);
    procedure printerComChange(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure printCase(Root: TJSONObject);
  private
    pIndex: Integer;
  public
    { Public declarations }
    Function GetPortNum(): Integer; // 获得端口号
    Function GetPortBaud(): Integer; // 获得端口波特率
  end;

var
  MainForm: TMainForm;
  printOffX, printOffY: Integer;

implementation

uses GFunction;

{$R *.dfm}
// 二代证相关API---------------------------------------------------------------------------------------
function SDT_GetCOMBaud(iPort: Integer; puiBaud: pDword): Integer; stdcall;
  external 'sdtapi.dll';

function SDT_SetCOMBaud(iPort: Integer; puiCurrBaund, puiSetBaund: Dword)
  : Integer; stdcall; external 'sdtapi.dll';

function SDT_StartFindIDCard(iPort: Integer; pucManaInfo: pByte;
  iIfOpen: Integer): Integer; stdcall; external 'sdtapi.dll';

function SDT_SelectIDCard(iPort: Integer; pucManaInfo: pByte; iIfOpen: Integer)
  : Integer; stdcall; external 'sdtapi.dll';

function SDT_ReadBaseMsg(iPort: Integer; pbyCHMsg: pByte; puiCHLen: pDword;
  pbyPHMsg: pByte; puiPHLen: pDword; iIfOpen: Integer): Integer; stdcall;
  external 'sdtapi.dll';

function SDT_ReadBaseFPMsg(iPort: Integer; pbyCHMsg: pByte; puiCHLen: pDword;
  pbyPHMsg: pByte; puiPHLen: pDword; pbyFPMsg: pByte; puiFPLen: pDword;
  iIfOpen: Integer): Integer; stdcall; external 'sdtapi.dll';

function SDT_GetSAMIDToStr(iPort: Integer; pSamID: pChar; iIfOpen: Integer)
  : Integer; stdcall; external 'sdtapi.dll';

function SDT_ReadNewAppMsg(iPort: Integer; pbyAppMsg: pByte; puiAppLen: pDword;
  iIfOpen: Integer): Integer; stdcall; external 'sdtapi.dll';

// 照片解码API---------------------------------------------------------------------------------------
function unpack(pcWltData: pChar; pcDstPicData: pChar; iIsSaveBmp: Integer)
  : Integer; cdecl; external 'DLL_File.dll';

function BaseImage(fn: string): string;
var
  m1: TMemoryStream;
  m2: TStringStream;
  str: string;
begin
  m1 := TMemoryStream.Create;
  m2 := TStringStream.Create;
  m1.LoadFromFile(fn);
  EncdDecd.EncodeStream(m1, m2); // 将m1的内容Base64到m2中
  str := m2.DataString;
  str := StringReplace(str, #13, '', [rfReplaceAll]);
  // 这里m2中数据会自动添加回车换行，所以需要将回车换行替换成空字符
  str := StringReplace(str, #10, '', [rfReplaceAll]);
  result := str; // 返回值为Base64的Stream
  m1.Free;
  m2.Free;
end;

// 获得端口号
function TMainForm.GetPortNum(): Integer;
var
  iPort: Integer;
begin
  iPort := ComboBox_Port.ItemIndex;

  if iPort = 0 then
    iPort := 1001;

  result := iPort;
end;

procedure TMainForm.IdHTTPServer1CommandGet(AContext: TIdContext;
  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
  jo: TJSONObject;
  s: string;
begin
  // 浏览器请求http://127.0.0.1:8008/index.html?a=1&b=2

  // ARequestInfo.QueryParams 返回  a=1b=2
  // ARequestInfo.Params.Values['name']   接收get,post过来的数据
  /// /webserver发文件
  { LFilename := ARequestInfo.Document;
    if LFilename = '/' then
    begin
    LFilename := '/'+trim(edit_index.Text);
    end;
    LPathname := RootDir + LFilename;
    if FileExists(LPathname) then begin
    AResponseInfo.ContentStream := TFileStream.Create(LPathname, fmOpenRead + fmShareDenyWrite);//发文件

    end
    else
    begin
    AResponseInfo.ResponseNo := 404;
    AResponseInfo.ContentText := '找不到' + ARequestInfo.Document;
    end; }
  // ShowMessage(  ARequestInfo.Document);
  if ARequestInfo.Document = '/person.json' then

  begin

    AResponseInfo.ContentEncoding := 'UTF-8';
    AResponseInfo.ContentType := 'text/json; charset=UTF-8';
    jo := read();
    jo.AddPair('action', ARequestInfo.Params.Values['action']);
    AResponseInfo.ContentText := ARequestInfo.Params.Values['callback'] + '(' +
      jo.ToString + ')';
    jo.Free;
    // Memo_Msg.Text := ARequestInfo.QueryParams;
  end
  else if ARequestInfo.Document = '/doPrint' then
  begin
    try
      s := TidUri.URLDecode(ARequestInfo.FormParams);
      // ShowMessage(s);

      printCase(TJSONObject.ParseJSONValue(s) as TJSONObject);
    except
       ShowMessage('打印出错！请检查打印机是否存在或者联机。');
    end;
  end
  else if ARequestInfo.Document = '/isOpen' then
  begin
    AResponseInfo.ContentEncoding := 'UTF-8';
    AResponseInfo.ContentType := 'text/json; charset=UTF-8';
    // jo := read();
    AResponseInfo.ContentText := ARequestInfo.Params.Values['callback']+'("open")';
  end;

  // 发xml文件        [dcc32 Error] Main.pas(127): E2010 Incompatible types: '' and 'string'
  { AResponseInfo.ContentType :='text/xml';
    AResponseInfo.ContentText:='<?xml version="1.0" encoding="utf-8"?>'
    +'<students>'
    +'<student sex = "male"><name>'+AnsiToUtf8('陈')+'</name><age>14</age></student>'
    +'<student sex = "female"><name>bb</name><age>16</age></student>'
    +'</students>'; }

  // 下载文件时，直接从网页打开而没有弹出保存对话框的问题解决
  // AResponseInfo.CustomHeaders.Values['Content-Disposition','attachment; filename="'+文件名+'"';
  // 替换 IIS
  { AResponseInfo.Server:='IIS/6.0';
    AResponseInfo.CacheControl:='no-cache';
    AResponseInfo.Pragma:='no-cache';
    AResponseInfo.Date:=Now; }
end;

// 获得端口波特率
function TMainForm.GetPortBaud(): Integer;
var
  strBaud: string;
  iBaud: Integer;
begin
  strBaud := ComboBox_Baud.Text;

  iBaud := strtoint(strBaud);

  result := iBaud;
end;

procedure TMainForm.Button_ClearClick(Sender: TObject);
begin
  memo_msg.Clear; // 清除当前文本
  Image_Photo.Picture.Assign(nil); // 清除当前照片
end;

procedure TMainForm.Button_ReadClick(Sender: TObject);
var
  res: TJSONObject;
begin
  res := read();
  if res.GetValue('type') = nil then
  begin
    ShowMessage(res.GetValue('message').value);
  end;
  res.Free;

end;

function TMainForm.read(): TJSONObject;
const
  byBmpHead: array [0 .. 13] of Byte = ($42, $4D, $CE, $97, $00, $00, $00, $00,
    $00, $00, $36, $00, $00, $00); // BMP图片格式头
  byBmpInfo: array [0 .. 39] of Byte = ($28, $00, $00, $00, // 结构所占用40字节
    $66, $00, $00, $00, // 位图的宽度102像素
    $7E, $00, $00, $00, // 位图的高度126像素
    $01, $00, // 目标设备的级别必须为1
    $18, $00, // 每个像素所需的位数24
    $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
    $00, $00, $00, $00, $00, $00, $00, $00, $00);
  // ......其他信息省略为0);     //BMP图像信息
var

  iPort, iResult: Integer;
  iIfOpen: Integer;
  uiDevBaud, uiCurBaud: Dword;

  szSAMID: array [0 .. 63] of AnsiChar;
  byManaID: array [0 .. 7] of Byte;

  byCHMsg: array [0 .. 256] of Byte; // 个人基本信息
  uiCHMsgSize: Dword; // 个人基本信息字节数
  byPHMsg: array [0 .. 1024] of Byte; // 照片信息
  uiPHMsgSize: Dword; // 照片信息字节数
  byFPMsg: array [0 .. 1024] of Byte; // 指纹信息
  uiFPMsgSize: Dword; // 指纹信息字节数

  byFgnCardSign: array [0 .. 1] of Byte; // 证件类型标识

  iIsSaveToBmp: Integer;

  byBgrBuffer: array [0 .. 38856] of Byte; // 指纹信息
  byBmpBuffer: array [0 .. 38862] of Byte; // 指纹信息

  iRgbSize: Integer;
  iBmpSize: Integer;

  // 居民身份证信息
  wszName: array [0 .. 15] of WideChar; // 姓名
  wszSex: array [0 .. 1] of WideChar; // 性别
  wszNation: array [0 .. 2] of WideChar; // 民族
  wszBirth: array [0 .. 8] of WideChar; // 出生日期
  wszAddr: array [0 .. 35] of WideChar; // 地址
  wszID: array [0 .. 18] of WideChar; // 身份证号
  wszDept: array [0 .. 15] of WideChar; // 签发机关
  wszStart: array [0 .. 8] of WideChar; // 有效期起始
  wszEnd: array [0 .. 8] of WideChar; // 有效期截至
  // 港澳台身份证信息
  wszPassID: array [0 .. 9] of WideChar; // 通行证号码
  wszIssNum: array [0 .. 2] of WideChar; // 签发次数
  wszCardSign: array [0 .. 1] of WideChar; // 证件类型标志
  // 外国人身份证信息
  wszFgnNameEN: array [0 .. 60] of WideChar; // Name EN
  wszFgnSex: array [0 .. 1] of WideChar; // Sex
  wszFgnCardNo: array [0 .. 15] of WideChar; // Card No
  wszFgnNation: array [0 .. 3] of WideChar; // Nationality
  wszFgnNameCN: array [0 .. 15] of WideChar; // Name CN
  wszFgnValidityBeg: array [0 .. 8] of WideChar; // Period of Validity Begin
  wszFgnValidityEnd: array [0 .. 8] of WideChar; // Period of Validity End
  wszFgnBirth: array [0 .. 8] of WideChar; // Date of Birth
  wszFgnCardVer: array [0 .. 2] of WideChar; // Card Version
  wszFgnIssAuth: array [0 .. 4] of WideChar; // Issuing Authority
  wszFgnCardSign: array [0 .. 1] of WideChar; // Card Sign

  strIDBase: string;

begin
  memo_msg.Clear; // 清除当前文本
  Image_Photo.Picture.Assign(nil); // 清除当前照片
  result := TJSONObject.Create;

  iIfOpen := 1; // 自动打开设备标志。如果设置为1，则在接口内部自动实现打开设备和关闭设备，无需调用者再添加。
  iPort := GetPortNum(); // 获得端口号
  /// /////////////////////////////////////////////////////////////////
  // 以下为串口波特率设置
  if (iPort <= 16) and (iPort >= 1) then // 是否为串口
  begin
    uiCurBaud := GetPortBaud(); // 获得当前波特率
    uiDevBaud := uiCurBaud;
    iResult := SDT_SetCOMBaud(iPort, uiDevBaud, uiCurBaud);
    // 尝试设置设备波特率，判断设备波特率和当前波特率是否相同
    if iResult <> $90 then
    begin
      // 设置失败，说明波特率不同，获取设备波特率
      iResult := SDT_GetCOMBaud(iPort, @uiDevBaud);
      if iResult <> $90 then
      begin
        result.AddPair('message', '获取设备波特率失败.ErrCode = ' + IntToStr(iResult));
        exit;
      end;

      // 修改设备波特率
      iResult := SDT_SetCOMBaud(iPort, uiDevBaud, uiCurBaud);
      if iResult <> $90 then
      begin
        result.AddPair('message', '修改设备波特率失败.ErrCode = ' + IntToStr(iResult));
        exit;
      end;
    end;
  end;

  // 获得设备SAM模块ID。
  // PS ：SAM模块ID为二代证设备唯一标志ID；
  // PS2：此函通常用来数来区分设备或判断设备是否连接正常；若只读卡信息的话无需添加此函数。
  iResult := SDT_GetSAMIDToStr(iPort, @szSAMID, iIfOpen);
  if iResult <> $90 then
  begin
    result.AddPair('message', '未连接设备。ErrCode = ' + IntToStr(iResult));
    exit;
  end;

  // 寻卡
  iResult := SDT_StartFindIDCard(iPort, @byManaID, iIfOpen);
  if iResult <> $9F then
  begin
    result.AddPair('message', '请将二代身份证放在阅读器正上方。ErrCode = ' + IntToStr(iResult));
    exit;
  end;

  // 选卡
  iResult := SDT_SelectIDCard(iPort, @byManaID, iIfOpen);
  if iResult <> $90 then
  begin
    result.AddPair('message', '选卡失败.ErrCode = ' + IntToStr(iResult));
    exit;
  end;

  // 读取身份证个人基本信息、照片信息和指纹信息；
  // PS：指纹信息需要专门的指纹比对设备，这里只获取加密的原始数据。
  iResult := SDT_ReadBaseFPMsg(iPort, @byCHMsg, @uiCHMsgSize, @byPHMsg,
    @uiPHMsgSize, @byFPMsg, @uiFPMsgSize, iIfOpen);
  if iResult = $21 then
  // 0501模块(一种老模块)无法读取指纹信息,会返回0x21错误，这里进行兼容处理；这种模块早就不用了，实际可以不做处理。
  begin
    iResult := SDT_ReadBaseMsg(iPort, @byCHMsg, @uiCHMsgSize, @byPHMsg,
      @uiPHMsgSize, iIfOpen); // 采用只读卡信息和照片，不读指纹信息的接口
  end;
  if iResult <> $90 then
  begin
    result.AddPair('message', '读取身份信息失败.ErrCode = ' + IntToStr(iResult));
    exit;
  end;

  // 解码照片数据，获得BGR格式数据
  // iIsSaveToBmp := 1;     //调用解码库unpack函数后，由接口自动生成名为zp.bmp的图片文件，该BMP文件可直接打开，不用B、R转换
  iIsSaveToBmp := 0; // 不自动生成zp.bmp图片

  // PS    ：解码库需要依赖授权文件（license.dat）；要确保“当前工作目录下”license.dat文件存在且正确，否则会返回-22和-12的错误
  // PS2   ：若设置iIsSaveToBmp = 1，即由接口自动生成zp.bmp文件，请确认“当前工作目录”具有写权限，否侧接口会崩溃（WIN7以上系统需注意此项）
  iResult := unpack(@byPHMsg, @byBgrBuffer, iIsSaveToBmp);
  if iResult <> 1 then
  begin
    result.AddPair('message', '照片解码失败.ErrCode = ' + IntToStr(iResult));
    exit;
  end;

  if iIsSaveToBmp = 0 then
  begin
    // 拼接BMP图片格式头，14字节
    CopyMemory(@byBmpBuffer[0], @byBmpHead[0], 14);

    // 拼接BMP图像信息40字节
    CopyMemory(@byBmpBuffer[14], @byBmpInfo[0], 40);

    // 将解码后的BGR格式数据进行B、R互换，拼接接到BmpBuffer图像信息后面
    iRgbSize := 38862 - 54;
    iResult := GFunction.bgr2rgb(@byBgrBuffer[0], 38556, @byBmpBuffer[54],
      @iRgbSize, 102, 126);
    if iResult <> 0 then
    begin
      result.AddPair('message', '照片数据B、R互换失败.');
      exit;
    end;

    iBmpSize := 54 + iRgbSize;
    iResult := GFunction.tool_WriteOneFile('zp1.bmp', @byBmpBuffer, iBmpSize);
    if iResult <> iBmpSize then
    begin
      result.AddPair('message', '保存照片数据失败.');
      exit;
    end;
  end; // if iIsSaveToBmp = 0 then

  Image_Photo.Picture.LoadFromFile('zp1.bmp'); // 显示图片

  result.AddPair('pic', BaseImage('zp1.bmp'));
  DeleteFile('zp1.bmp');

  // 截断数据获得证件类型标识
  CopyMemory(@byFgnCardSign[0], @byCHMsg[248], 2);

  // 判断类型标识，更新UI显示信息
  // 大写字母'I'表示为外国人居住证，卡片返回身份信息数据默认为宽字符，这里采用直接判断字节的方法（宽字符大写字母'I'由2字节组成，分别为0x49 0x00）
  if (byFgnCardSign[0] = $49) and (byFgnCardSign[1] = 0) then
  begin
    // 截取个人信息数据。信息采用UNICODE存储，具体格式参可见
    // 《银管发[2016]348号中国人民银行营业管理部转发中国人民银行办公厅关于做好外国人永久居留证芯片机读改造工作的通知.doc》

    result.AddPair('type', 'foreign');
    CopyMemory(@wszFgnNameEN, @byCHMsg[0], 120);
    CopyMemory(@wszFgnSex, @byCHMsg[120], 2);
    CopyMemory(@wszFgnCardNo, @byCHMsg[122], 30);
    CopyMemory(@wszFgnNation, @byCHMsg[152], 6);
    CopyMemory(@wszFgnNameCN, @byCHMsg[158], 30);
    CopyMemory(@wszFgnValidityBeg, @byCHMsg[188], 16);
    CopyMemory(@wszFgnValidityEnd, @byCHMsg[204], 16);
    CopyMemory(@wszFgnBirth, @byCHMsg[220], 16);
    CopyMemory(@wszFgnCardVer, @byCHMsg[236], 4);
    CopyMemory(@wszFgnIssAuth, @byCHMsg[240], 8);
    CopyMemory(@wszFgnCardSign, @byCHMsg[248], 2);

    strIDBase := '读卡成功.' + #13#10#13#10;
    strIDBase := strIDBase + '英文姓名：' + wszFgnNameEN + #13#10#13#10;
    strIDBase := strIDBase + '性别：' + wszFgnSex + #13#10#13#10;
    strIDBase := strIDBase + '永久居留证号：' + wszFgnCardNo + #13#10#13#10;
    strIDBase := strIDBase + '国籍或所在地区代码：' + wszFgnNation + #13#10#13#10;
    strIDBase := strIDBase + '中文姓名：' + wszFgnNameCN + #13#10#13#10;
    strIDBase := strIDBase + '证件签发日期：' + wszFgnValidityBeg + #13#10#13#10;
    strIDBase := strIDBase + '证件终止日期：' + wszFgnValidityEnd + #13#10#13#10;
    strIDBase := strIDBase + '出生日期：' + wszFgnBirth + #13#10#13#10;
    strIDBase := strIDBase + '证件版本号：' + wszFgnCardVer + #13#10#13#10;
    strIDBase := strIDBase + '当此申请受理机关代码：' + wszFgnIssAuth + #13#10#13#10;
    strIDBase := strIDBase + '证件类型标识：' + wszFgnCardSign + #13#10#13#10;
  end
  else if (byFgnCardSign[0] = $4A) and (byFgnCardSign[1] = 0) then
  // 大写字母'I'（0x4A 0x00）表示为港澳台居住证
  begin
    // 截取个人信息数据。信息采用UNICODE存储，具体格式参可见《港澳台居民居住证机读信息规范（试行版）》
    result.AddPair('type', 'HK_M_TW');
    CopyMemory(@wszName, @byCHMsg[0], 30);
    CopyMemory(@wszSex, @byCHMsg[30], 2);
    CopyMemory(@wszBirth, @byCHMsg[36], 16);
    CopyMemory(@wszAddr, @byCHMsg[52], 70);
    CopyMemory(@wszID, @byCHMsg[122], 36);
    CopyMemory(@wszDept, @byCHMsg[158], 30);
    CopyMemory(@wszStart, @byCHMsg[188], 16);
    CopyMemory(@wszEnd, @byCHMsg[204], 16);
    CopyMemory(@wszPassID, @byCHMsg[220], 18);
    CopyMemory(@wszIssNum, @byCHMsg[238], 4);
    CopyMemory(@wszCardSign, @byCHMsg[248], 2);

    strIDBase := '读卡成功.' + #13#10#13#10;
    strIDBase := strIDBase + '姓名：' + wszName + #13#10#13#10;
    strIDBase := strIDBase + '性别：' + wszSex + #13#10#13#10;
    strIDBase := strIDBase + '出生：' + wszBirth + #13#10#13#10;
    strIDBase := strIDBase + '住址：' + wszAddr + #13#10#13#10;
    strIDBase := strIDBase + '身份证号：' + wszID + #13#10#13#10;
    strIDBase := strIDBase + '签发机关：' + wszDept + #13#10#13#10;
    strIDBase := strIDBase + '有效期起始：' + wszStart + #13#10#13#10;
    strIDBase := strIDBase + '有效期结束：' + wszEnd + #13#10#13#10;
    strIDBase := strIDBase + '通行证号码：' + wszPassID + #13#10#13#10;
    strIDBase := strIDBase + '签发次数：' + wszIssNum + #13#10#13#10;
    strIDBase := strIDBase + '证件类型标识：' + wszCardSign + #13#10#13#10;
  end
  else
  begin
    // 截取个人信息数据。信息采用UNICODE存储，具体格式参可见《二代证机读信息说明.doc》

    result.AddPair('type', 'CHINA');

    CopyMemory(@wszName, @byCHMsg[0], 30);
    CopyMemory(@wszSex, @byCHMsg[30], 2);
    CopyMemory(@wszNation, @byCHMsg[32], 4);
    CopyMemory(@wszBirth, @byCHMsg[36], 16);
    CopyMemory(@wszAddr, @byCHMsg[52], 70);
    CopyMemory(@wszID, @byCHMsg[122], 36);
    CopyMemory(@wszDept, @byCHMsg[158], 30);
    CopyMemory(@wszStart, @byCHMsg[188], 16);
    CopyMemory(@wszEnd, @byCHMsg[204], 16);

    strIDBase := '读卡成功.' + #13#10#13#10;
    result.AddPair('message', '读卡成功.');
    strIDBase := strIDBase + '姓名：' + wszName + #13#10#13#10;
    result.AddPair('personName', wszName);
    strIDBase := strIDBase + '性别：' + wszSex + #13#10#13#10;
    result.AddPair('personSex', wszSex);
    strIDBase := strIDBase + '民族：' + wszNation + #13#10#13#10;
    result.AddPair('nation', wszNation);
    strIDBase := strIDBase + '出生：' + wszBirth + #13#10#13#10;
    result.AddPair('birth', wszBirth);
    strIDBase := strIDBase + '住址：' + wszAddr + #13#10#13#10;
    result.AddPair('personAddr', wszAddr);
    strIDBase := strIDBase + '身份证号：' + wszID + #13#10#13#10;
    result.AddPair('personId', wszID);
    strIDBase := strIDBase + '签发机关：' + wszDept + #13#10#13#10;
    result.AddPair('dept', wszDept);
    strIDBase := strIDBase + '有效期起始：' + wszStart + #13#10#13#10;
    result.AddPair('start', wszStart);
    strIDBase := strIDBase + '有效期结束：' + wszEnd + #13#10#13#10;
    result.AddPair('end', wszEnd);
  end;

  memo_msg.Text := strIDBase;

end;

procedure TMainForm.printCase(Root: TJSONObject);

var
  value, center, vcenter: string;
  r: TRect;
  jo: TJSONObject;
  printDatas, rectArray: TJSONArray;
  offX, offY, i, align: Integer;
  // img: TBitMap;

begin
  // value := '汉字进行自动换行汉字进行自动换行汉字进行自动换行';

  align := DT_CENTER; // DT_BOTTOM

  SetPaperHeight(strtoint(Root.GetValue('height').value));
  SetPaperWidth(strtoint(Root.GetValue('width').value));
  offX := strtoint(Root.GetValue('offX').value);
  offY := strtoint(Root.GetValue('offY').value);
  // Printer.title := Root.Values['theParties'].value;

  Printer.BeginDoc;
  Printer.Canvas.Font.name := '宋体';
  Printer.Canvas.Font.Size := 20;
  printDatas := TJSONArray(Root.GetValue('printDatas'));

  // img := TBitMap.Create();
  // img.LoadFromFile('E:\Desktop\ee.bmp');

  // Printer.Canvas.CopyRect(Rect(0, 0, 2*img.Width, 2*img.Height), img.Canvas,
  // Rect(0, 0, img.Width, img.Height));

  // img.Destroy;
  try
    for i := 0 to printDatas.Count - 1 do
    begin

      center := '';
      vcenter := '';
      jo := TJSONObject(printDatas.Get(i));
      value := jo.GetValue('name').value;
      if value = '' then
        continue;
      if jo.GetValue('center') <> nil then
        center := jo.GetValue('center').value;

      if jo.GetValue('vcenter') <> nil then
        vcenter := jo.GetValue('vcenter').value;

      rectArray := TJSONArray(jo.GetValue('rect'));

      with r do
      begin
        left := strtoint(rectArray.Items[0].value) + offX + printOffX;
        top := strtoint(rectArray.Items[1].value) + offY + printOffY;
        Right := strtoint(rectArray.Items[2].value) + offX + printOffX;
        Bottom := strtoint(rectArray.Items[3].value) + offY + printOffY;
      end;
      if center <> 'true' then
      begin
        align := DT_LEFT;
      end
      else
      begin
        align := DT_CENTER;
      end;
      if vcenter <> 'true' then
      begin
        align := align + DT_BOTTOM;
      end
      else
      begin
        align := align + DT_VCENTER;
      end;
      try
        PrintTextRect(r, value, strtoint(jo.GetValue('fontSize').value), align);
      finally

      end;

    end;
  finally

  end;

  Printer.EndDoc;
  Root.Free;
end;

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  saveSetting();
end;

procedure TMainForm.FormCreate(Sender: TObject);
var
  iPos: Integer;
  strItem: string;

begin
  // 设置端口ComboBox显示内容
  ComboBox_Port.Items.Add('USB');

  for iPos := 1 to 16 do
  begin
    strItem := Format('COM%02d', [iPos]);
    ComboBox_Port.Items.Add(strItem);
  end;
  ComboBox_Port.ItemIndex := 0; // 默认USB口

  // 设置波特率ComboBox显示内容
  ComboBox_Baud.Items.Add('115200');
  ComboBox_Baud.Items.Add('57600');
  ComboBox_Baud.Items.Add('38400');
  ComboBox_Baud.Items.Add('19200');
  ComboBox_Baud.Items.Add('9600');
  ComboBox_Baud.ItemIndex := 0; // 默认115200

  memo_msg.Clear; // 清除当前文本
  loadSetting();
end;

procedure TMainForm.loadSetting();
var
  userSetting: TIniFile;
  strList: TStringList;
begin
  printerCom.Items := Printer.Printers;
  userSetting := TIniFile.Create(ExtractFilePath(ParamStr(0)) + 'setting.ini');

  offXSE.value := userSetting.ReadInteger('print', 'offX', 0);
  offYSE.value := userSetting.ReadInteger('print', 'offY', 0);

  printOffX := offXSE.value;
  printOffY := offYSE.value;
  if userSetting.ReadBool('print', 'first', true) then
  begin
    ShowMessage('第一次运行会弹出“Windows安全警报”对话框，可以忽略。');
    ShowMessage('身份证阅读器的设置一般不需要配置！');
    ShowMessage('请先选择正确的针式打印机，并设置相应的偏移量。');
  end;

  pIndex := userSetting.ReadInteger('print', 'pIndex', 0);
  if (pIndex < 0) or (pIndex > printerCom.Items.Count) then
    pIndex := 0;
  printerCom.ItemIndex := pIndex;
  Printer.PrinterIndex := pIndex;
  userSetting.Free;

end;

procedure TMainForm.offXSEChange(Sender: TObject);
begin
  printOffX := offXSE.value;
  printOffY := offYSE.value;
end;

procedure TMainForm.offYSEChange(Sender: TObject);
begin
  printOffX := offXSE.value;
  printOffY := offYSE.value;
end;

procedure TMainForm.printerComChange(Sender: TObject);
begin
  pIndex := printerCom.ItemIndex;
  Printer.PrinterIndex := pIndex;
end;

procedure TMainForm.saveSetting();
var
  userSetting: TIniFile;
  strList: TStringList;
begin

  userSetting := TIniFile.Create(ExtractFilePath(ParamStr(0)) + 'setting.ini');

  userSetting.WriteInteger('print', 'offX', offXSE.value);
  userSetting.WriteInteger('print', 'offY', offYSE.value);
  userSetting.WriteBool('print', 'first', false);
  userSetting.WriteInteger('print', 'pIndex', pIndex);

  userSetting.Free;

end;

procedure TMainForm.LinkLabel2Click(Sender: TObject);
begin
  offXSE.value := offXSE.value - 1;
  printOffX := offXSE.value;
  printOffY := offYSE.value;
end;

procedure TMainForm.LinkLabel3Click(Sender: TObject);
begin
  offXSE.value := offXSE.value + 1;
  printOffX := offXSE.value;
  printOffY := offYSE.value;
end;

procedure TMainForm.LinkLabel4Click(Sender: TObject);
begin
  offYSE.value := offYSE.value - 1;
  printOffX := offXSE.value;
  printOffY := offYSE.value;
end;

procedure TMainForm.LinkLabel5Click(Sender: TObject);
begin
  offYSE.value := offYSE.value + 1;
  printOffX := offXSE.value;
  printOffY := offYSE.value;
end;

end.
